/*
 * Copyright (C) 2017-2019 Alibaba Group Holding Limited
 */

/******************************************************************************
 * @file     camera_ov5647.c
 * @brief    CSI Source File for camera Driver
 * @version  V2.0
 * @date     10. Nov 2019
 ******************************************************************************/

/* LOG_LEVEL: 0: Err; 1: Err&Warn; 2: Err&Warn&Info; 3: Err&Warn&Info&Debug */
#define LOG_LEVEL 0
#include <syslog.h>

#include <stdio.h>
#include <string.h>
// #include <csi_config.h>
#include <soc.h>
#include <io.h>
#include <pin.h>
#include <drv_usi_iic.h>
#include <drv_camera.h>
#include "camera_ov5647.h"

#define ERR_CAMERA(errno) (CSI_DRV_ERRNO_CAMERA_BASE | errno)
#define CAMERA_NULL_PARAM_CHK(para) HANDLE_PARAM_CHK(para, ERR_CAMERA(DRV_ERROR_PARAMETER))

extern void mdelay(uint32_t ms);
extern ov_priv_t camera_handle[CONFIG_CAMERA_NUM];
extern const struct regval_list sensor_oe_enable_regs[3];
extern const struct regval_list sensor_oe_disable_regs[3];

static void camera_iic_event_cb_fun(int32_t idx, iic_event_e event)
{
    if (event == IIC_EVENT_TRANSFER_DONE) {
        int i;
        for (i = 0; i < CONFIG_CAMERA_NUM; i++) {
            ov_priv_t *camera_priv = &(camera_handle[i]);
            if (camera_priv != NULL && camera_priv->init_flag &&
                camera_priv->commu_info.iic_idx == idx) {
                camera_priv->commu_info.cb_transfer_flag = 1;
                return;
            }
        }
        LOG_E("No camera match, idx=%d\n", idx);
    }
}

int32_t csi_camera_start(camera_handle_t handle, camera_config_t *config)
{
    int32_t ret;
    uint8_t resetval;
    CAMERA_NULL_PARAM_CHK(handle);
    ov_priv_t *camera_priv = (ov_priv_t *)handle;

    if (config->height > OV5647_WINDOW_HEIGHT_DEF || config->width > OV5647_WINDOW_WIDTH_DEF) {
        LOG_E("height(%d) or width(%d) not supported\n", config->height, config->width);
        return -1;
    }

    if (config->format != CAMERA_DATA_FORMAT_RAW8) {
        LOG_E("format(%d) not supported\n", config->format);
        return -1;
    }

    LOG_D("CSI_SUBDEV_PWR_ON!\n");
    ret = ov_set_stby(&camera_priv->commu_info, CSI_STBY_OFF);
    if(ret < 0) {
        LOG_E("soft stby off failed!\n");
        return -1;
    }

    ret = ov_write_array(&camera_priv->commu_info,
                         (struct regval_list *)sensor_oe_enable_regs,
                         ARRAY_SIZE(sensor_oe_enable_regs));
    if(ret < 0) {
        LOG_E("enable oe failed!\n");
        return -1;
    }
    ov_write(&camera_priv->commu_info, 0x4800, 0x25);
    ov_stream_off(&camera_priv->commu_info);
    ov_write(&camera_priv->commu_info, 0x0100, 0x00);
    ov_write(&camera_priv->commu_info, 0x0103, 0x01);
    /* wait for camera ready, reference ov5647_ommivision 2.5.1 */
    mdelay(5);
    ret = ov_write_array(&camera_priv->commu_info,
                         (struct regval_list *)ov5647_640x480,
                         ARRAY_SIZE(ov5647_640x480));
    if (ret < 0) {
        LOG_E("ov_write_array failed!\n");
        return -1;
    }
    /* Set width */
    /* TIMING_X_OUTPUT_SIZE d:0x0A DVP output horizontal width[11:8] */
    ov_write(&camera_priv->commu_info, 0x3808, (uint8_t)((config->width >> 8) & 0xf));
    /* TIMING_X_OUTPUT_SIZE d:0x20 DVP output horizontal width[7:0] */
    ov_write(&camera_priv->commu_info, 0x3809, (uint8_t)(config->width & 0xff));
    /* Set height */
    /* TIMING_Y_OUTPUT_SIZE d:0x07 DVP output vertical width[11:8] */
    ov_write(&camera_priv->commu_info, 0x380a, (uint8_t)((config->height >> 8) & 0xf));
    /* TIMING_Y_OUTPUT_SIZE d:0x98 DVP output vertical width[7:0] */
    ov_write(&camera_priv->commu_info, 0x380b, (uint8_t)(config->height & 0xff));
    ret = ov_write_array(&camera_priv->commu_info,
                         (struct regval_list *)ov5647_640x480_2,
                         ARRAY_SIZE(ov5647_640x480_2));
    if (ret < 0) {
        LOG_E("ov_write_array() failed, ret=%d\n", ret);
        return -1;
    }
    /* vc is 0, format is raw8 */
    ov_write(&camera_priv->commu_info, 0x4814, 0x2a);
    ov_read(&camera_priv->commu_info, 0x0100, &resetval);
    if (!resetval&0x01) {
        //LOG_D("device was in software standby");
        ov_write(&camera_priv->commu_info, 0x0100, 0x01);
    }
    /* wait for camera VDD ready, reference ov5647_ommivision 2.5.2 */
    mdelay(20);
    ov_write(&camera_priv->commu_info, 0x4800, 0x24);
    ov_stream_on(&camera_priv->commu_info);
    return 0;
}

int32_t csi_camera_stop(camera_handle_t handle)
{
    int32_t ret;
    CAMERA_NULL_PARAM_CHK(handle);
    ov_priv_t *camera_priv = (ov_priv_t *)handle;

    LOG_D("CSI_SUBDEV_PWR_OFF!\n");
    ret = ov_write_array(&camera_priv->commu_info,
                         (struct regval_list *)sensor_oe_disable_regs,
                         ARRAY_SIZE(sensor_oe_disable_regs));
    if(ret < 0) {
        LOG_E("disable oe failed!\n");
        return -1;
    }
    //software standby on
    ret = ov_set_stby(&camera_priv->commu_info, CSI_STBY_ON);
    if(ret < 0) {
        LOG_E("soft stby failed!\n");
        return -1;
    }
    return 0;
}

camera_handle_t csi_camera_initialize(int32_t idx, camera_event_cb_t cb_event, void *user_data)
{
    if (idx < 0 || idx >= CONFIG_CAMERA_NUM) {
        LOG_E("CAMERA input id error.\n");
        return NULL;
    }

    ov_priv_t *camera_priv = &camera_handle[idx];
    if (camera_priv->init_flag == 1) {
        LOG_E("CAMERA has been initialized\n");
        return NULL;
    }

    int32_t ret;
    int32_t iic_idx = -1;
    if (idx == 0) {
        #ifdef EXAMPLE_CAMERA0_IIC_IDX
        iic_idx = EXAMPLE_CAMERA0_IIC_IDX;
        #endif
    } else if (idx == 1) {
        #ifdef EXAMPLE_CAMERA1_IIC_IDX
        iic_idx = EXAMPLE_CAMERA1_IIC_IDX;
        #endif
    } else if (idx == 2) {
        #ifdef EXAMPLE_CAMERA2_IIC_IDX
        iic_idx = EXAMPLE_CAMERA2_IIC_IDX;
        #endif
    }
    if (iic_idx < 0) {
        LOG_E("Camera idx(%d) not defined iic id\n", idx);
        return NULL;
    }
    iic_handle_t iic_handle = csi_iic_initialize(iic_idx, camera_iic_event_cb_fun);
    ret = csi_iic_config(iic_handle, IIC_MODE_MASTER, IIC_BUS_SPEED_STANDARD,
                         IIC_ADDRESS_7BIT, OV5647_IIC_SLAVE_ADDR);
    if (ret < 0) {
        LOG_E("CAMERA(%d) init fail\n", idx);
        return NULL;
    }

    camera_priv->base_info.pll_clock = 96000000;
    camera_priv->base_info.height = OV5647_WINDOW_HEIGHT_DEF;
    camera_priv->base_info.width = OV5647_WINDOW_WIDTH_DEF;
    camera_priv->base_info.chipid_high = OV5647_CHIPID_HIGH;
    camera_priv->base_info.chipid_low = OV5647_CHIPID_LOW;
    camera_priv->status.standby = 1;
    camera_priv->status.pwoer = 0;
    camera_priv->user_data = user_data;

    camera_priv->commu_info.iic_idx = iic_idx;
    camera_priv->commu_info.iic_handle = iic_handle;
    camera_priv->commu_info.slave_addr = OV5647_IIC_SLAVE_ADDR;
    camera_priv->commu_info.cb_transfer_flag = 0;
    camera_priv->init_flag = 1;

    /* Check the id of camera */
    ret = ov_check_id(&camera_priv->commu_info, OV5647_CHIPID_HIGH, OV5647_CHIPID_LOW);
    if (ret < 0) {
        LOG_E("CAMERA(%d) check id fail\n", idx);
        csi_camera_uninitialize((camera_handle_t)camera_priv);
        return NULL;
    }
    return (camera_handle_t)camera_priv;
}

int32_t csi_camera_uninitialize(camera_handle_t handle)
{
    CAMERA_NULL_PARAM_CHK(handle);
    ov_priv_t *camera_priv = (ov_priv_t *)handle;

    csi_iic_uninitialize(camera_priv->commu_info.iic_handle);
    memset(camera_priv, 0, sizeof(ov_priv_t));
    return 0;
}

